package fctuc.dei.myTwitter.server;

import static fctuc.dei.myTwitter.constants.MyTwitterConstants.RMI_LISTENER_PORT;
import static fctuc.dei.myTwitter.constants.MyTwitterConstants.TCP_LISTENER_PORT;
import static fctuc.dei.myTwitter.constants.MyTwitterConstants.TCP_LISTENER_PORT_ALTERNATIVE;
import static fctuc.dei.myTwitter.constants.MyTwitterConstants.SERVER_FILES;

import java.io.IOException;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.Hashtable;

import fctuc.dei.myTwitter.com.Connection;
import fctuc.dei.myTwitter.com.MT;
import fctuc.dei.myTwitter.com.Message;
import fctuc.dei.myTwitter.com.MessageHandler;
import fctuc.dei.myTwitter.dao.TweetDAO;
import fctuc.dei.myTwitter.dao.UserDAO;
import fctuc.dei.myTwitter.database.DbWrapper;
import fctuc.dei.myTwitter.pojo.Tweet;
import fctuc.dei.myTwitter.pojo.User;

public class MyTwitterServer extends Thread implements MessageHandler{

	//TCPServer tcpServer;
	private ServerSocket listener;
	//TODO: preciso de uma hashtable com todos os users?
	private Hashtable<Integer, UserConnection> connections;
	private UserDAO userDAO = new UserDAO();
	private TweetDAO tweetDAO = new TweetDAO();
	private NioThread nioServer;
	private RMIServerImpl rmiServer;

	public static void main(String[] args){

		MyTwitterServer server = new MyTwitterServer(TCP_LISTENER_PORT);
		server.run();

	}
	
	public MyTwitterServer(int listenerPort){

		try {
			//TCP SERVER
			this.listener = new ServerSocket(listenerPort);
		} catch (IOException e) {
			
			try {
				this.listener = new ServerSocket(TCP_LISTENER_PORT_ALTERNATIVE);
			} catch (IOException e1) {
				System.err.println("Could not start TCP server at port "+ this.listener.getLocalPort());
			}
		}
		
		System.out.println("TCP Server up and running. Listening at port: "+listenerPort);
		//Set up Servidor RMI
		try {
			System.getProperties().put("java.security.policy", SERVER_FILES + "/policy.all");
            System.setSecurityManager(new RMISecurityManager());
            
			rmiServer = new RMIServerImpl(this);

//			Naming.rebind("rmi://localhost:" + RMI_LISTENER_PORT + "/MyTwitterServer", rmiServer);
			Registry r = LocateRegistry.createRegistry(RMI_LISTENER_PORT);
			r.rebind("MyTwitterServer", rmiServer);

		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("RMI Server up and running. Listening at port: "+RMI_LISTENER_PORT);
		//Arrancar servidor NIO
		//		nioServer = new NioThread(this);
		//		new Thread(nioServer).start();
		/*
		 * Fica a escuta de novas ligacoes
		 */
		
		DbWrapper.getInstance().connect();
	}
	
	public void run(){
		this.connections = new Hashtable<Integer, UserConnection>();
		
		try{
			while(true){
				new Thread(new SocketThread(listener.accept(), this)).start();
			}
		}catch (IOException io){
			io.printStackTrace();
		}
	}

	@Override
	synchronized public boolean handleMessage(Message m, Connection c) {
		UserConnection connection = (UserConnection) c;
		Message reply;
		
//		System.out.println(">>>Incoming message >>> " + m.type.toString());
		switch(m.type){
		case REGISTER:
			System.out.println("REGISTER");
			reply = new Message(MT.REGISTER_RESULT);
			
			int id = userDAO.registerUser((User) m.get("USER"));
			if( id > 0){
				connection.user = (User) m.get("USER");
				connection.user.setId(id);
				reply.set("RESULT", "OK");
				reply.set("USER",connection.user);
			}
			else
				reply.set("RESULT","FAIL");
			
			this.connections.put(connection.user.getId(), connection);
			connection.send(reply);
			break;
		case LOGIN:
			
			if(m.get("RECONNECT").equals("FALSE")){
				connection.user = this.userDAO.authenticateUser((User) m.get("USER"));
				if (connection.user != null){
					//se ja esta ligado...
					if(this.connections.containsKey(connection.user.getId())){
							connection.send(MT.LOGIN_RESULT,"RESULT", "FAIL");
							break;
					}
					this.connections.put(connection.user.getId(), connection);
					connection.send(MT.LOGIN_RESULT,"RESULT", "OK","USER", connection.user);
				}
				else
					connection.send(MT.LOGIN_RESULT,"RESULT", "FAIL");
			}
			else
				/*
				 * Quando se trata de um RECONNECT a autenticacao e automatica
				 */
			{
				System.out.println("Reconnecting client");
				connection.user = (User) m.get("USER");
				this.connections.put(connection.user.getId(), connection);
			}
			 break;
		case LOGOUT:
//			userEndConnection(m.getInt("USER_ID"));
			connection.send(new Message(MT.LOGOUT));
			userEndConnection(connection.user.getId());
			break;
		case FOLLOW:
			int followed_id = this.userDAO.getByUsername(m.getStr("USERNAME"));
			reply = new Message(MT.FOLLOW);
			if(followed_id == 0){
				reply.set("RESULT", "FAIL");
				reply.set("CAUSE", "No such user");
			}
			else{
				if (this.userDAO.follow(connection.user.getId(), followed_id)){
					reply.set("RESULT", "OK");
					reply.set("USER", m.getStr("USERNAME"));
				}
				else{
					reply.set("RESULT", "FAIL");
					reply.set("CAUSE", "Already following user: "+m.getStr("USERNAME"));
				}
			}
			connection.send(reply);
			break;
		case SEARCH:
			ArrayList<User> searchResult;
			searchResult = userDAO.searchUser(m.getStr("PARAMS"));
			connection.send(MT.SEARCH, "RESULTS", searchResult);
			break;
		case TWEET:
			Tweet t = (Tweet) m.get("TWEET");
			tweetDAO.insertTweet(t);
			ArrayList<User> fl;
			fl = userDAO.getFollowers(connection.user);
			t.setAuthor(connection.user.getUsername());
			for(User u : fl){
				if(connections.containsKey(u.getId())){
					UserConnection uc = connections.get(u.getId());
					uc.send(new Message(MT.TWEET, "TWEET", t));
				}
			}
//			System.out.println("TWEET RECEBIDO ["+t.getMessage() +"] de " + connection.user.getUsername());
			break;
		case LIST_TWEETS:
			ArrayList<Tweet> tweetList = tweetDAO.getTweets(connection.user);
			reply = new Message(MT.LIST_TWEETS);
			reply.set("TWEET_LIST", tweetList);
			connection.send(reply);
			break;
		case FOLLOWERS:
			ArrayList<User> followers;
			followers = userDAO.getFollowers(connection.user);
			connection.send(MT.FOLLOWERS, "FOLLOWERS", followers);
			break;
		case FOLLOWING:
			ArrayList<User> following;
			following = userDAO.getFollowing(connection.user);
			connection.send(MT.FOLLOWING, "FOLLOWING", following);
			break;
		default:
			System.out.println("DEFAULT");
			break;
		}
		return false;
	}
	
	private void userEndConnection(int user_id){
		connections.get(user_id).terminate();
		connections.remove(user_id);
//		System.out.println("\nuser ["+user_id+"] ended connection");
	}

	@Override
	synchronized public void handleNioMessage(Message m, SelectionKey key) {
		// TODO Auto-generated method stub
		Message reply;
		
//		System.out.println(">>>Incoming message >>> " + m.type.toString());
		switch(m.type){
		case REGISTER:
			System.out.println("REGISTER");
			reply = new Message(MT.REGISTER_RESULT);
			
			int id = userDAO.registerUser((User) m.get("USER"));
			if( id > 0){
				User u = (User) m.get("USER");
				u.setId(id);
				reply.set("RESULT", "OK");
				reply.set("USER",u);
				nioServer.utilizadoresNio.put(key, u);
			}
			else
				reply.set("RESULT","FAIL");
			
//			this.connections.put(connection.user.getId(), connection);
			nioServer.realSend(reply, key);
			break;
			
			
		case LOGIN:

			if(m.get("RECONNECT").equals("FALSE")){
				//se ja esta ligado...
				boolean exists = false;
				User us = (User) m.get("USER");
				
				for(SelectionKey user : nioServer.utilizadoresNio.keySet())
					if(nioServer.utilizadoresNio.get(user).getUsername().equalsIgnoreCase(us.getUsername())){
						exists = true;
						break;
					}
				if(exists){
					nioServer.realSend(new Message(MT.LOGIN_RESULT,"RESULT", "FAIL"), key);
					nioServer.keysTerminar.put(key, true);
					break;
				}

				User u = this.userDAO.authenticateUser((User) m.get("USER"));
				if (u != null){
					nioServer.utilizadoresNio.put(key, u);
					nioServer.realSend(new Message(MT.LOGIN_RESULT,"RESULT", "OK","USER", u), key);
				}
				else{
					nioServer.realSend(new Message(MT.LOGIN_RESULT,"RESULT", "FAIL"),key);
					break;
				}
			}
			else
				/*
				 * Quando se trata de um RECONNECT a autenticacao e automatica
				 */
			{
				System.out.println("Reconnecting client");
				User u = (User) m.get("USER");
				nioServer.utilizadoresNio.put(key, u);
			}
			break;
		case LOGOUT:
			//TODO: semaforo para esperar?
			nioServer.realSend(new Message(MT.LOGOUT), key);
			nioServer.utilizadoresNio.remove(key);
			nioServer.keysTerminar.put(key, true);
			break;
		case FOLLOW:
			int followed_id = this.userDAO.getByUsername(m.getStr("USERNAME"));
			reply = new Message(MT.FOLLOW);
			if(followed_id == 0){
				reply.set("RESULT", "FAIL");
				reply.set("CAUSE", "No such user");
			}
			else{
				if (this.userDAO.follow(nioServer.utilizadoresNio.get(key).getId(), followed_id)){
					reply.set("RESULT", "OK");
					reply.set("USER", m.getStr("USERNAME"));
				}
				else{
					reply.set("RESULT", "FAIL");
					reply.set("CAUSE", "Already following user: "+m.getStr("USERNAME"));
				}
			}
			nioServer.realSend(reply, key);
			break;
		case SEARCH:
			ArrayList<User> searchResult;
			searchResult = userDAO.searchUser(m.getStr("PARAMS"));
			nioServer.realSend(new Message(MT.SEARCH, "RESULTS", searchResult), key);
			break;
		case TWEET:
			Tweet t = (Tweet) m.get("TWEET");
			tweetDAO.insertTweet(t);
			t.setAuthor(nioServer.utilizadoresNio.get(key).getUsername());
			ArrayList<User> fl;
			fl = userDAO.getFollowers(nioServer.utilizadoresNio.get(key));
			for(User u : fl){
				for(SelectionKey sKey : nioServer.utilizadoresNio.keySet()){
					if(sKey.equals(key))
						continue;
					if(nioServer.utilizadoresNio.containsKey(sKey))
						if(nioServer.utilizadoresNio.get(sKey).getUsername().equalsIgnoreCase(u.getUsername())){
							nioServer.realSend(new Message(MT.TWEET, "TWEET", t), sKey);
							sKey.interestOps(SelectionKey.OP_WRITE|SelectionKey.OP_READ);
						}
				}
			}
					
			
//			System.out.println("TWEET RECEBIDO ["+t.getMessage() +"] de " + nioServer.utilizadoresNio.get(key).getUsername());
			break;
		case LIST_TWEETS:
			ArrayList<Tweet> tweetList = tweetDAO.getTweets(nioServer.utilizadoresNio.get(key));
			reply = new Message(MT.LIST_TWEETS);
			reply.set("TWEET_LIST", tweetList);
			nioServer.realSend(reply, key);
			break;
		case FOLLOWERS:
			ArrayList<User> followers;
			followers = userDAO.getFollowers(nioServer.utilizadoresNio.get(key));
			nioServer.realSend(new Message(MT.FOLLOWERS, "FOLLOWERS", followers),key);
			break;
		case FOLLOWING:
			ArrayList<User> following;
			following = userDAO.getFollowing(nioServer.utilizadoresNio.get(key));
			nioServer.realSend(new Message(MT.FOLLOWING, "FOLLOWING", following), key);
			break;
		default:
			System.out.println("DEFAULT");
			break;
		}
	}

}
